import os

import HumanMemory

class CCppParseState:
    START = 0
    INCLUDE = 1
    C_COMMENT = 8
    CPP_COMMENT = 9
    STRING = 16
    IDENTIFIER = 17
    INTEGER_LITERAL = 18
    OPERATOR = 19

class CCppAnalyzer:
    keywordList = ['__abstract', '__alignof', '__asm', '__assume', '__based', '__box', '__cdecl', '__declspec', '__delegate 2', '__event', '__except', '__fastcall', '__finally', '__forceinline', '__gc', '__hook', '__identifier', '__if_exists', '__if_not_exists', '__inline', '__int16', '__int32', '__int64', '__int8', '__interface', '__leave', '__m128', '__m128d', '__m128i', '__m64', '__multiple_inheritance', '__nogc', '__noop', '__pin', '__property', '__raise', '__sealed', '__single_inheritance', '__stdcall', '__super', '__thiscall', '__try', '__except', '__finally', '__try_cast', '__unaligned', '__unhook', '__uuidof', '__value', '__virtual_inheritance', '__w64', '__wchar_t', 'wchar_t', 'abstract', 'array', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const', 'const_cast', 'continue', 'decltype', 'default', 'delegate', 'delete', 'deprecated', 'dllexport', 'dllimport', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'enum class', 'enum struct', 'event', 'explicit', 'extern', 'false', 'finally', 'float', 'for', 'for each, in', 'friend', 'friend_as', 'gcnew', 'generic', 'goto', 'if', 'initonly', 'inline', 'int', 'interface class', 'interface struct', 'interior_ptr', 'literal', 'long', 'mutable', 'naked', 'namespace', 'new', 'noinline', 'noreturn', 'nothrow', 'novtable', 'nullptr', 'operator', 'private', 'property', 'property', 'protected', 'public', 'ref class', 'ref struct', 'register', 'reinterpret_cast', 'return', 'safecast', 'sealed', 'selectany', 'short', 'signed', 'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this', 'thread', 'throw', 'true', 'try', 'typedef', 'typeid', 'typeid', 'typename', 'union', 'unsigned', 'using', 'uuid', 'value class', 'value struct', 'virtual', 'void', 'volatile', 'while']
    
    def __init__(self):
        self.text = ''
        self.ccppParseState = CCppParseState.START
        self.codeLineCount = 0
        self.commentLineCount = 0
        self.identifierList = [] # duplicates not allowed
        self.identifierIndexStart = -1
        self.identifierIndexEnd = -1
        self.textIndex = -1
        self.lineStart = True
        self.skipNextChar = False
        self.identifierIsPreprocessorDirective = False
        self.humanMemory = HumanMemory.HumanMemory()
        self.lineWithIdentifier = False
        self.lineWithIntegerLiteral = False
        self.lineWithString = False
        self.lineWithComment = False
        self.identifierUsageCount = 0

    def setText(self, text):
        self.text = text
    
    def resetParseState(self):
        self.ccppParseState = CCppParseState.START
        self.textIndex = 0
        self.lineStart = True
        
    def parse(self):
        setLineStart = False
        for c in self.text:
            #print('text index = ' + str(self.textIndex) + ' ccpp parse state = ' + str(self.ccppParseState) + ' line start = ' + str(self.lineStart))
            if not self.skipNextChar:
                if c == '\r' or c == '\n':
                    if self.ccppParseState == CCppParseState.INCLUDE:
                        self.ccppParseState = CCppParseState.START
                    elif self.ccppParseState == CCppParseState.CPP_COMMENT:
                        self.ccppParseState = CCppParseState.START
                    setLineStart = True
                    if self.lineWithIdentifier or self.lineWithIntegerLiteral or self.lineWithString:
                        self.codeLineCount += 1
                    if self.lineWithComment:
                        self.commentLineCount += 1
                    self.lineWithIdentifier = False
                    self.lineWithIntegerLiteral = False
                    self.lineWithString = False
                    self.lineWithComment = False
                self.parseChar(c)
            else:
                self.skipNextChar = False
            self.textIndex += 1
            if self.lineStart:
                self.lineStart = False
            if setLineStart:
                setLineStart = False
                self.lineStart = True
    
        #self.identifierList = [item for item in self.identifierList if item not in self.keywordList]
    
    def parseChar(self, c):
        if self.ccppParseState == CCppParseState.START:
            if self.lineStart:
                if c == '#':
                    nc = self.getNextChar()
                    if not nc == None:
                        if self.text[self.textIndex + 1:].startswith('include'):
                            self.ccppParseState = CCppParseState.INCLUDE
                            self.skipNextChar = True
                    if not self.ccppParseState == CCppParseState.INCLUDE:
                        self.identifierIsPreprocessorDirective = True
            if c == '"':
                self.ccppParseState = CCppParseState.STRING
                self.lineWithString = True
            elif c == '/':
                nc = self.getNextChar()
                if nc == '*':
                    self.ccppParseState = CCppParseState.C_COMMENT
                    self.skipNextChar = True
                elif nc == '/':
                    self.ccppParseState = CCppParseState.CPP_COMMENT
                    self.skipNextChar = True
            elif c.isalpha() or c == '_':
                self.ccppParseState = CCppParseState.IDENTIFIER
                self.identifierIndexStart = self.textIndex
                self.lineWithIdentifier = True
            elif c.isdigit():
                if c == '0':
                    nc = self.getNextChar()
                    if nc == 'x':
                        self.ccppParseState = CCppParseState.INTEGER_LITERAL
                        self.skipNextChar = True
                        self.lineWithIntegerLiteral = True
                else:
                    self.ccppParseState = CCppParseState.INTEGER_LITERAL
                    self.lineWithIntegerLiteral = True
            elif c.isspace():
                pass
            if self.ccppParseState == CCppParseState.C_COMMENT or self.ccppParseState == CCppParseState.CPP_COMMENT:
                self.lineWithComment = True
        elif self.ccppParseState == CCppParseState.INCLUDE:
            pass
        elif self.ccppParseState == CCppParseState.C_COMMENT:
            if c == '*':
                nc = self.getNextChar()
                if nc == '/':
                    self.ccppParseState = CCppParseState.START
                    self.skipNextChar = True
        elif self.ccppParseState == CCppParseState.CPP_COMMENT:
            pass
        elif self.ccppParseState == CCppParseState.STRING:
            if c == '"':
                self.ccppParseState = CCppParseState.START
        elif self.ccppParseState == CCppParseState.IDENTIFIER:
            if not (c.isalnum() or c == '_'):
                self.ccppParseState = CCppParseState.START
                if not self.identifierIsPreprocessorDirective:
                    self.identifierIndexEnd = self.textIndex
                    identifier = self.text[self.identifierIndexStart:self.identifierIndexEnd]
                    #print(str(self.identifierIndexStart) + '  ' + str(self.identifierIndexEnd))
                    self.identifierIndexStart = -1
                    self.identifierIndexEnd = -1
                    self.identifierUsageCount += 1
                    if not identifier in self.keywordList:
                        if not identifier in self.identifierList:
                            self.identifierList.append(identifier)
                        humanMemoryCheck = self.humanMemory.check(identifier)
                        if not humanMemoryCheck:
                            self.humanMemory.insert(identifier)
                else:
                    self.identifierIsPreprocessorDirective = False
        elif self.ccppParseState == CCppParseState.INTEGER_LITERAL:
            if not c.isalnum():
                self.ccppParseState = CCppParseState.START
        elif self.ccppParseState == CCppParseState.OPERATOR:
            self.ccppParseState = CCppParseState.START
        else:
            self.ccppParseState = CCppParseState.START
            print('Invalid C/C++ parse state')
    
    def getNextChar(self):
        if self.textIndex + 1 < len(self.text):
            result = self.text[self.textIndex + 1]
        else:
            result = None
        return result
    
    def printInfo(self):
        #print('[%s]' % ', '.join(map(str, self.identifierList)))
        identifierCount = len(self.identifierList)
        print('Identifier count = ' + str(identifierCount))
        print('Code line count = ' + str(self.codeLineCount))
        print('Comment line count = ' + str(self.commentLineCount))
        commentRate = 0
        lineCount = self.codeLineCount + self.commentLineCount
        if lineCount > 0:
            commentRate = self.commentLineCount / lineCount
            print('Comment rate = ' + str(format(commentRate, '.2f')) + ' %')
        if self.codeLineCount > 0:
            print('Identifier density (identifier usage count per code line count) = ' + str(format(self.identifierUsageCount / self.codeLineCount, '.2f')))
        self.humanMemory.printCheckInfo()
